package room

import (
	"encoding/json"
	"gitee.com/jamon/espoir_go/common"
	"gitee.com/jamon/espoir_go/common/utils"
	"gitee.com/jamon/espoir_go/frame"
	"gitee.com/jamon/espoir_go/global"
	"gitee.com/jamon/espoir_go/logger"
	"gitee.com/jamon/espoir_go/route"
	"github.com/google/uuid"
	"sync"
	"tankserver/lib"
)

type Manager struct {
	roomMap utils.Map[string, *BaseRoom] // roomID -> room

	// userID -> roomID/gameID
	userRoomMap utils.Map[uint64, string] // userID -> roomID/gameID
}

var (
	roomMgr  *Manager
	roomOnce sync.Once
)

func GetRoomManager() *Manager {
	roomOnce.Do(func() {
		roomMgr = &Manager{
			roomMap:     utils.NewMap[string, *BaseRoom](),
			userRoomMap: utils.NewMap[uint64, string](),
		}
	})
	return roomMgr
}

func (mgr *Manager) CreateRoom(userID uint64, gameType uint8, roomType uint8) (roomID string, err error) {
	uid, err := uuid.NewUUID()
	if err != nil {
		return "", err
	}
	roomID = uid.String()
	room := NewBaseRoom(roomID, gameType, roomType)
	room.MembersStatus.Put(userID, ReadyStatus_NOT_READY)
	mgr.roomMap.Put(roomID, room)
	mgr.userRoomMap.Put(userID, roomID)
	return
}

func (mgr *Manager) joinRoom(userID uint64, roomID string, gid int64) (err error) {
	room, ok := mgr.roomMap.GetWithGid(roomID, gid)
	if !ok {
		return
	}
	room.MembersStatus.PutWithGid(userID, ReadyStatus_NOT_READY, gid)
	mgr.userRoomMap.PutWithGid(userID, roomID, gid)
	return
}

func (mgr *Manager) JoinMatchRoom(userID uint64, gameType uint8) (roomID string, err error) {
	gid := int64(userID)
	mgr.roomMap.LockRangeWithGid(func(key string, value *BaseRoom) bool {
		if value.RoomType == RoomType_Match && value.GameType == gameType && !value.isFull() {
			err := mgr.joinRoom(userID, key, gid)
			if err != nil {
				return true
			}
			roomID = key
			return true
		}
		return false
	}, gid)
	if roomID == "" {
		roomID, err = mgr.CreateRoom(userID, gameType, RoomType_Match)
		if err != nil {
			return
		}
	}
	return
}

func (mgr *Manager) CheckStartGame(roomID string) bool {
	room, ok := mgr.roomMap.Get(roomID)
	if !ok {
		logger.Warn("room not exist", logger.Kwargs{"roomID": roomID})
		return false
	}
	if room.isFull() && room.isAllReady() {
		return true
	}
	return false
}

func (mgr *Manager) StartGame(roomID string) {
	room, ok := mgr.roomMap.Get(roomID)
	if !ok {
		logger.Warn("room not exist", logger.Kwargs{"roomID": roomID})
		return
	}

	room.GameStatus = GameStatus_RUNNING
	users := global.GetUserManager().GetSessionIDByUserIDs(room.MembersStatus.Keys())
	frameServer, err := frame.GetFrameManager().CreateServer(room.GameID, users)
	if err != nil {
		logger.Error("create frame server failed", logger.Kwargs{"roomID": roomID})
		return
	}
	room.FrameServer = frameServer
	room.FrameServer.Status = common.BattleStatus_RUNNING

	// 通知玩家游戏开始
	var userIDs []uint64
	room.MembersStatus.LockRange(func(key uint64, value uint8) {
		userIDs = append(userIDs, key)
	})

	logger.Info("broadcast game start", logger.Kwargs{"user_ids": userIDs})
	pushData := lib.StartGamePushData{
		RandSeed:  room.RandSeed,
		TeamUsers: users,
	}
	pushDataBytes, err := json.Marshal(pushData)
	if err != nil {
		logger.Error("json marshal failed", logger.Kwargs{"err": err})
		return
	}
	go common.SafeRun(func() {
		err := route.GetController().NarrowCastToClient(userIDs, lib.MsgID_ROOM_START, pushDataBytes)
		if err != nil {
			logger.Error("broadcast failed", logger.Kwargs{"user_ids": userIDs})
			return
		}
	})
}

func (mgr *Manager) CheckGameOver(roomID string) bool {
	room, ok := mgr.roomMap.Get(roomID)
	if !ok {
		logger.Warn("room not exist", logger.Kwargs{"roomID": roomID})
		return false
	}
	if room.isFull() && room.isAllOver() {
		return true
	}
	return false
}

func (mgr *Manager) GameOver(roomID string, isWin bool) {
	room, ok := mgr.roomMap.Get(roomID)
	if !ok {
		logger.Warn("room not exist", logger.Kwargs{"roomID": roomID})
		return
	}

	room.GameStatus = GameStatus_GAME_OVER
	room.FrameServer.Status = common.BattleStatus_GAME_OVER

	// 通知玩家游戏结束
	var userIDs []uint64
	room.MembersStatus.LockRange(func(key uint64, value uint8) {
		userIDs = append(userIDs, key)
	})

	logger.Info("broadcast game over", logger.Kwargs{"user_ids": userIDs})
	pushData := lib.EndGamePushData{
		RoomID: roomID,
		IsWin:  isWin,
	}
	pushDataBytes, err := json.Marshal(pushData)
	if err != nil {
		logger.Error("json marshal failed", logger.Kwargs{"err": err})
		return
	}
	go common.SafeRun(func() {
		err := route.GetController().NarrowCastToClient(userIDs, lib.MsgID_ROOM_GAME_OVER, pushDataBytes)
		if err != nil {
			logger.Error("broadcast failed", logger.Kwargs{"user_ids": userIDs})
			return
		}
	})
	frame.GetFrameManager().DeleteServer(roomID)
	mgr.roomMap.Remove(roomID)
}

func (mgr *Manager) SetReadyStatus(userID uint64, status uint8) (roomID string) {
	roomID, ok := mgr.userRoomMap.Get(userID)
	if !ok {
		logger.Warn("user not in room", logger.Kwargs{"userID": userID})
		return
	}
	room, ok := mgr.roomMap.Get(roomID)
	if !ok {
		logger.Warn("room not exist", logger.Kwargs{"roomID": roomID})
		return
	}
	room.MembersStatus.Put(userID, status)
	return
}

func (mgr *Manager) AddOp(userID uint64, jsonOp string) (roomID string) {
	roomID, ok := mgr.userRoomMap.Get(userID)
	if !ok {
		logger.Warn("user not in room", logger.Kwargs{"userID": userID})
		return
	}
	room, ok := mgr.roomMap.Get(roomID)
	if !ok {
		logger.Warn("room not exist", logger.Kwargs{"roomID": roomID})
		return
	}
	room.FrameServer.AddOp(userID, jsonOp)
	return
}
